package edu.fim.core.service.impl;

import java.util.List;

import edu.fim.aspect.Transactional;
import edu.fim.core.exception.BmiOutOfRangeException;
import edu.fim.core.exception.CaloriesThresholdExceededException;
import edu.fim.core.exception.ServiceException;
import edu.fim.core.service.ConsumerService;
import edu.fim.core.util.BmiCategories;
import edu.fim.data.dao.ConsumerDAO;
import edu.fim.data.dao.DAO;
import edu.fim.data.dao.exception.DAOException;
import edu.fim.data.dao.factory.DAOFactory;
import edu.fim.presentation.model.bean.Consumer;
import edu.fim.presentation.model.bean.Food;

public class ConsumerServiceImpl implements ConsumerService {
	
	public Consumer findById(Integer id) throws ServiceException {
		DAO<Consumer, Integer> consumerDAO = DAOFactory.getInstance().createConsumerDao();
		try {
			return consumerDAO.findById(id);
		}
		catch (DAOException e) {
			e.printStackTrace();
			throw new ServiceException(e.getMessage());
		}
	}

	public List<Consumer> listAll() throws ServiceException {
		DAO<Consumer, Integer> cd = DAOFactory.getInstance().createConsumerDao();
		try {
			return cd.findAll();
		}
		catch (DAOException e) {
			e.printStackTrace();
			throw new ServiceException(e.getMessage());
		}
	}
	
	public Consumer findByUserAndPassword(String username, String password) throws ServiceException {
		ConsumerDAO dao = DAOFactory.getInstance().createConsumerDao();
		String[] fields = { "username", "password" };
		Object[] values = { username, password };
		try {
			return dao.findByUserAndPassword(username, password);
		}
		catch (DAOException e) {
			throw new ServiceException(e.getMessage(), e);
		}
	}

	public void update(Consumer consumer) throws ServiceException {
		DAO<Consumer, Integer> dao = DAOFactory.getInstance().createConsumerDao();
		try {
			dao.update(consumer);
		} catch (DAOException e) {
			throw new ServiceException("Error during update.", e);
		}
	}

	public void delete(Consumer consumer) throws ServiceException {
		DAO<Consumer, Integer> dao = DAOFactory.getInstance().createConsumerDao();
		try {
			dao.delete(consumer);
		}
		catch (DAOException e) {
			e.printStackTrace();
			throw new ServiceException(e.getMessage());
		}
	}
	
	@Transactional
	public void setCaloriesThreshold(Consumer consumer, Double newThreshold) throws ServiceException {
		ConsumerDAO consumerDAO = DAOFactory.getInstance().createConsumerDao();
		consumerDAO.updateCaloriesThreshold(consumer, newThreshold);
	}

	public void increaseCalories(Consumer consumer, Food food) throws ServiceException {
		consumer.setConsumedCalories(consumer.getConsumedCalories() + food.getCalories());
		ConsumerDAO consumerDAO = DAOFactory.getInstance().createConsumerDao();
		Consumer managedEntity = consumerDAO.findById(consumer.getId());
		managedEntity.setConsumedCalories((consumer.getConsumedCalories()));
		if (limiarExcedido(managedEntity)) {
			throw new CaloriesThresholdExceededException(
					consumer.getName() + ", your calories threshold was exceeded.");
		}
	}
	
	public void updatePreferences(Consumer consumer) {
		ConsumerDAO consumerDAO = DAOFactory.getInstance().createConsumerDao();
		Consumer managedEntity = consumerDAO.findById(consumer.getId());
		managedEntity.setPreferences(consumer.getPreferences());
	}

	public boolean limiarExcedido(Consumer consumer) {
		if (consumer.getConsumedCalories() > consumer.getCaloriesThreshold()) {
			return true;
		}
		return false;
	}

	public BmiCategories determineWeightSituation(Consumer consumer) throws BmiOutOfRangeException {
		Double bmiValue = calculateBmi(consumer);
		for (BmiCategories bmiCategory : BmiCategories.values()) {
			if (bmiCategory.hasValue(bmiValue)) {
				return bmiCategory;
			}
		}
		throw new BmiOutOfRangeException("The BMI is out of range.");
	}

	/**
	 * Auxiliary method that encapsulates the BMI formula.
	 * 
	 * @param consumer
	 * @return
	 * @throws ArithmeticException
	 */
	private Double calculateBmi(Consumer consumer) throws ArithmeticException {
		Double dividendo = (consumer.getHeight() * consumer.getHeight());
		if (dividendo.equals(0.0)) {
			throw new ArithmeticException("Division by zero!");
		}
		return consumer.getWeight() / dividendo;
	}
}